
#include "pch.h"
#include "PlayGameState.h"
#include "Game.h"
#include <vector>
#include <list>
#include "Ball.h"

const double PI = 3.1415926;

using namespace std;
using namespace Math;

namespace
{
    double sign(double d) { return d > 0 ? 1 : d < 0 ? -1 : 0; }
}

namespace GameStates
{

    PlayGameState::PlayGameState(Core::Game &game)
         : Core::GameState(game),
          rectRight(600), rectLeft(200), rectTop(200), rectBottom(600), center(400), epsilon(.000001), gamma(.000001) // greek constants used to account for rounding errors
    {
    }
    
    ///
    /// Add the initialization of the game state here. Game logic is initalized
    /// in NewGame().
    ///
    void PlayGameState::Start()
    {
        BackgroundSprite = game.resourceManager->GetSprite("background");
        BallSprite = game.resourceManager->GetSprite("ball");
        PlayerBallSprite = game.resourceManager->GetSprite("playerBall");
        gameFont = game.resourceManager->GetFont("gameFont");
        cursor = game.resourceManager->GetSprite("cursor");
        playAgainButton = game.resourceManager->GetSprite("playAgainButton");
        quitButton = game.resourceManager->GetSprite("quitButton");
		BallCollisionEffect = game.resourceManager->GetEffect("BallCollision.ogg");
		WallCollisionEffect = game.resourceManager->GetEffect("WallCollision.ogg");
		
        menuItems.push_back(MenuItem("Play Again", playAgainButton, 400, 400));
        menuItems.push_back(MenuItem("Quit", quitButton, 400, 500));

        windowHandle = game.hge->System_GetState(HGE_HWND);
	
		srand((unsigned int)time(0));

        NewGame();
		BackgroundSprite->SetHotSpot(240,240);
    }
    
    ///
    /// Resets all game settings
    ///
    void PlayGameState::NewGame()
    {
        bool GameOver = false;

        BallHolder.clear();
        BallHolder.reserve(20);

        for(int i = 0; i < 3; ++i) {
            AddBall();
            BallHolder[i].Alpha(255);
        }
        timeUntilNextBall = game.GetGameTickCount() + 10000;
        timeStart = game.GetGameTickCount();  //Stores when the player clicked "Play" on the menu
        gameMode = Playing;
        score = 0;
        rotationPos = 0;  //note that positive rotation is clockwise and negative is counterclockwise.
        rotationVel = 0;
    }

    ///
    /// All game logic is processed here
    ///
    bool PlayGameState::Update()
    {
		//check if player has alt-tabed out or switched windows to stop the timer and properly suspend play
        

        delta = game.hge->Timer_GetDelta();
		//set new positions based on new velocities
		for(vector<Ball>::iterator iter = BallHolder.begin (); iter < BallHolder.end (); ++iter )
        {
            (*iter).OldPos((*iter).Pos());
            (*iter).Pos((*iter).Pos() + (*iter).Vel() * (1/delta));
		}
		
		HandleCollisions();
		
		if(game.hge->Input_GetKeyState(HGEK_ESCAPE))
		    return false;
        
		//render background and player ball
		//needs to be before the font so that it does not cover it up
        rotationPos = rotationPos + rotationVel/100;

		rotationVel -= abs(rotationVel) > 0 ? sign(rotationVel)/1000.0 : 0;

		BackgroundSprite->RenderEx(400, 400, (float)rotationPos); //note that that position is the center
        PlayerBallSprite->RenderEx((float)playerBall.Pos().x, (float)playerBall.Pos().y, 0, .5, 0);
        
        POINT mousePos;
        GetCursorPos(&mousePos);
        ScreenToClient(windowHandle, &mousePos);

        if(gameMode == Playing)
        {
			////manage mouse collisions///
            playerBall.Pos(Vector2d((float)mousePos.x, (float)mousePos.y));      
            const static Vector2d center = Vector2d(rectRight - rectLeft, rectBottom - rectTop);
            double angle = Angle(center, playerBall.Pos()) - rotationPos;
            double dist = Distance(playerBall.Pos(), center);
            Vector2d straightPos = Vector2d(sin(angle)*dist, cos(angle)*dist) + center;
            
            if(straightPos.y < rectTop + 14) straightPos = Vector2d(straightPos.x, rectTop + 14);
			else if(straightPos.y > rectBottom - 16) straightPos = Vector2d(straightPos.x, rectBottom - 16);

			if(straightPos.x < rectLeft + 14) straightPos = Vector2d(rectLeft + 14, straightPos.y);
            else if(straightPos.x > rectRight - 16) straightPos = Vector2d(rectRight - 16, straightPos.y);
            
            angle = Angle(center, straightPos) - rotationPos;
            dist = Distance(straightPos, center);
            playerBall.Pos(Vector2d(sin(angle)*dist, cos(angle)*dist) + center);
        }
	            
        if(gameMode == FallingBalls)
		{
			// Make balls fall downwards if the player has lost
			playerBall.Vel(playerBall.Vel() + Vector2d(0, .001f));
			playerBall.Pos(playerBall.Pos() + playerBall.Vel() * (1/delta));

            for(vector<Ball>::iterator iter2 = BallHolder.begin (); iter2 < BallHolder.end (); ++iter2 )
                (*iter2).Vel((*iter2).Vel() + Vector2d(0, .001f));
	
			///display score///
			stringstream ss;
			ss << "You lasted " << timeEnd << " seconds";
			gameFont->Render(300, 250, 0, ss.str().c_str());
			ss.str("");

			ss << "Number of balls: " << BallHolder.size();
			gameFont->Render(300, 280, 0, ss.str().c_str());
            ss.str("");
            
			ss << "Your score is " << (int)score;
            gameFont->Render(300, 320, 0, ss.str().c_str());
            ss.str("");
			/////game needs to stop updating here so balls stop dropping and timer stops/////
			
            bool hasClicked = game.hge->Input_GetKeyState(HGEK_LBUTTON);
            
            // Render and update menu
            for(std::vector<MenuItem>::iterator iter = menuItems.begin(); iter != menuItems.end(); ++iter)
            {
                iter->TellMousePos((float)mousePos.x, (float)mousePos.y);
                iter->Render();
                if(hasClicked && iter->IsColliding())
                {
                    if(iter->GetName() == "Play Again")
                    {
                        NewGame();
                    }
                    else if(iter->GetName() == "Quit")
                    {
                        return false;
                    }
                }
            }

            cursor->Render((float)mousePos.x, (float)mousePos.y);
		}

        //render new ball positions
        for ( std::vector <Ball>::iterator iter = BallHolder.begin (); iter < BallHolder.end (); iter++ )
        {
            // Make opaque if transparent
            if((*iter).Alpha() < 255)
                (*iter).Alpha((*iter).Alpha() + .05 * (1/delta));
            if((*iter).Alpha() > 255)
                (*iter).Alpha(255);
                
            //render balls
            BallSprite->SetColor((((int)(*iter).Alpha()) << 24) | 0xFF0000);
            BallSprite->RenderEx((float)(*iter).Pos().x, (float)(*iter).Pos().y, 0, (float)(*iter).GetMass(), (float)(*iter).GetMass() );
        }
        
        // First ball spawns after 10 seconds, second after 20, third after 30, and so forth
														// balls will not spawn after game ends
		if (gameMode == Playing)
		{
			score = CalculateScore();
			if(timeUntilNextBall < game.GetGameTickCount())
			{
				AddBall();
				timeUntilNextBall = game.GetGameTickCount() + 10000 * ((int)BallHolder.size() - 2);
			}
				///display time till ball spawns///
			stringstream ss;
			ss << "      Time until next ball spawns: " << (timeUntilNextBall - game.GetGameTickCount())/1000 + 1;
			gameFont->Render(200, 10, 0, ss.str().c_str());
			ss.str("");

			ss << "   Your score is " << (int)score;
			gameFont->Render(700, 10, 0, ss.str().c_str());
            ss.str("");
		}
		
		return true;
	}
    ///
    /// Cleanup goes here
    ///
    void PlayGameState::End()
    {

    }

    void PlayGameState::HandleCollisions()
    {
        if(gameMode == Playing)
        {
            // See if the balls non-player balls collide with the walls
            for(vector<Ball>::iterator iter = BallHolder.begin (); iter < BallHolder.end (); ++iter )
				FindWallCollision(*iter);

			for(vector<Ball>::iterator iter = BallHolder.begin (); iter < BallHolder.end (); ++iter )
            {
                // Check for player lose collision
                if(Distance((*iter).Pos(), playerBall.Pos()) <= (29+58*(*iter).GetMass())/2)
                {
					//if a ball just spawns or the game is just beginning, in case the ball spawns near the player, 
					//we do not want it to cause a collision and end the game
					if (iter == BallHolder.end() - 1 && game.hge->Timer_GetTime() < 10 * ( BallHolder.size() - 3) + .75 || game.GetGameTickCount() - timeStart < 1000) //less that one second
						break;

					gameMode = FallingBalls;
					timeEnd = (game.GetGameTickCount() - timeStart) / 1000;

					//Adjust playerball velocity
					playerBall.Vel(Vector2d(((rand () % 20) - 10)/200.f, 0));
                    
					// Apply a movement to the side to all balls and remove vertical velocity
					for(vector<Ball>::iterator iter2 = BallHolder.begin (); iter2 < BallHolder.end (); ++iter2 )
						(*iter2).Vel(Vector2d(((rand () % 20) - 10)/200.f, 0));

					break;
                }
            }	
        }
		//check for collisions between balls
			//check for collisions between balls
		double distance;
		Vector2d testPoint;
        for(unsigned int i = 0; i < BallHolder.size() - 1; ++i)
		{
            for(unsigned int j = i+1; j < BallHolder.size(); ++j)
			{
				distance = Distance( BallHolder[i].Pos(), BallHolder[j].Pos() );
                if ( distance <= ( 29*(BallHolder[i].GetMass() + BallHolder[j].GetMass()) ) + epsilon)  // times 29 is equivlant to times 58/2 (the average of their two masses)
                {
					game.hge->Effect_Play(BallCollisionEffect);
              
					if(distance < (58 * BallHolder[i].GetMass()) + (58 * BallHolder[j].GetMass()))
                    {
						BallHolder[i].Pos(( (-BallHolder[i].Vel()) * epsilon * 100000) + BallHolder[i].OldPos());
						BallHolder[j].Pos(( (-BallHolder[j].Vel()) * epsilon * 100000) + BallHolder[j].OldPos());
					}

					 //If two balls collide, the x and y velocities of each respective ball are reversed.
					 //Mass is factored into this equation.

					Ball iter1 = BallHolder[i];

					BallHolder[i].Vel( ( BallHolder[i].Vel() * (BallHolder[i].GetMass() - BallHolder[j].GetMass()) + (2*BallHolder[j].GetMass()*BallHolder[j].Vel()) ) / (BallHolder[i].GetMass() + BallHolder[j].GetMass()) );
					BallHolder[j].Vel( ( BallHolder[j].Vel() * (BallHolder[j].GetMass() - iter1.GetMass())         + (2*iter1.GetMass()*iter1.Vel()) )                 / (iter1.GetMass()        + BallHolder[j].GetMass()) );
                }
			}
		}	
	}
	void PlayGameState::AddBall()
	{
		vector <Ball>::iterator iter2;
		bool truth = false;
		while (truth == false)
		{
			truth = true;
			BallHolder.push_back(Ball());
			iter2 = BallHolder.end() - 1;
			(*iter2).Alpha(0);
			for (vector <Ball>::iterator iter = BallHolder.begin(); iter < BallHolder.end() - 1; iter++) //ball should not test distance against itself
			{
				if (  Distance( (*iter2).Pos(), playerBall.Pos()) <= (31 + 58 * (*iter2).GetMass())/2 || Distance ( (*iter2).Pos(), (*iter).Pos() ) <= 30 * ((*iter).GetMass() + (*iter2).GetMass()) )
				{
					BallHolder.pop_back();
					truth = false;
					break;
				}
			}
		}
	}
	double PlayGameState::CalculateScore()
	{
		double largestDistance = -1, sumVel = 0, sumMass = 0, temp;
		for (vector <Ball>::iterator iter = BallHolder.begin(); iter < BallHolder.end(); iter++)
		{
			temp = Distance(playerBall.Pos(), (*iter).Pos());
			if (temp > largestDistance)
			{
				largestDistance = temp;
			}
            sumVel += (*iter).Vel().Mag();
			sumMass += (*iter).GetMass();
		}
		return (score + 100 * (sumVel + BallHolder.size() + (sumMass/5)) * 1/largestDistance * 10);
	}

	void PlayGameState::CalculateRotation(const Ball& ball)
	{
		double torque;
		//magnitude of power that is transferred to the box  //Note:Not power in the physics sense
		torque = ( (ball.GetMass()+1) * (1 + ball.Vel().Mag()) * sin(atan(ball.GetSlope()))  ) /8; //arctan of slope gives angle of elevation/depression

		//magnitude of power transferred to the ball.
		rotationVel = torque + rotationVel;
	}

	Math::Vector2d PlayGameState::FindSlope(Vector2d vec1, Vector2d vec2)
	{
		return  Vector2d (vec2.x - vec1.x, vec2.y - vec1.y);
	}

	void PlayGameState::FindWallCollision (Ball& ball)
	{
        bool wallcol = false;
        const static Vector2d center = Vector2d(rectRight - rectLeft, rectBottom - rectTop);
        Vector2d straightPos = ball.Pos().Rotate(-rotationPos, center);
        Vector2d straightVel = ball.Vel().Rotate(-rotationPos, Vector2d(0, 0));
        double ballRadius = ball.GetMass() * 29;
        
        // See if the ball has hit an edge. Constrain the ball's position so that
        // it doesn't move past the edge, and invert the velocity. This acts on the
        // box as if it wasn't angled, making things slightly more managable.
        // Top edge
        if(straightPos.y < rectTop + ballRadius)
        {
         //   CalculateRotation(ball);
            straightPos = Vector2d(straightPos.x, rectTop + ballRadius + gamma);			
			straightVel = Vector2d(straightVel.x, abs(straightVel.y));
            wallcol = true;
        }

        // Bottom edge
        else if(straightPos.y > rectBottom - ballRadius)
        {
           // CalculateRotation(ball);
            straightPos = Vector2d(straightPos.x, rectBottom - ballRadius - gamma);
			straightVel = Vector2d(straightVel.x, -abs(straightVel.y));
            wallcol = true;
        }
        // Left edge
        else if(straightPos.x < rectLeft + ballRadius)
        {
         //   CalculateRotation(ball);
            straightPos = Vector2d(rectLeft + ballRadius + gamma, straightPos.y);
            straightVel = Vector2d(abs(straightVel.x), straightVel.y);
            wallcol = true;
        }
        // Right edge
        else if(straightPos.x > rectRight - ballRadius)
        {
           // CalculateRotation(ball);
            straightPos = Vector2d(rectRight - ballRadius - gamma, straightPos.y);
            straightVel = Vector2d(-abs(straightVel.x), straightVel.y);
            wallcol = true;
        }
        
		//Undoes first rotation (position)
        ball.Pos(straightPos.Rotate(rotationPos, center));

        if(wallcol)
        {
			//Undoes first rotation (velocity)
            ball.Vel(straightVel.Rotate(rotationPos, Vector2d(0, 0)));
            game.hge->Effect_Play(WallCollisionEffect);
        }
	}

}  // namespace gamestates